package httpclient

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"
	"net/url"
	"strings"
	"time"
)

type HTTPClient struct {
	client  *http.Client
	baseURL string
	Headers map[string][]string
}

type option func(*HTTPClient)

func WithBaseURL(baseURL string) option {
	return func(h *HTTPClient) {
		h.baseURL = baseURL
	}
}
func WithHeaders(headers map[string][]string) option {
	return func(h *HTTPClient) {
		h.Headers = headers
	}
}
func New(options ...option) *HTTPClient {
	client := &HTTPClient{
		client: &http.Client{
			Timeout: 10 * time.Second,
		},
	}
	for _, option := range options {
		option(client)
	}
	return client
}

// HTTPError 自定义HTTP错误
type HTTPError struct {
	StatusCode int
	Message    string
}

func (e *HTTPError) Error() string {
	return fmt.Sprintf("HTTP request failed with status %d: %s", e.StatusCode, e.Message)
}
func (h *HTTPClient) Request(method, path string, opts ...RequestOption) ([]byte, error) {
	options := &requestOptions{
		ctx: context.Background(),
	}
	//构建完整的URL,如果设置了baseURL,则使用baseURL+path,否则使用path
	u, err := url.Parse(h.baseURL + path)
	if err != nil {
		return nil, err
	}
	if options.query != nil {
		u.RawQuery = options.query.Encode()
	}
	for _, opt := range opts {
		opt(options)
	}
	//设置超时时间
	if options.timeout > 0 {
		h.client.Timeout = options.timeout
	}
	//准备请求体
	var bodyReader io.Reader
	if options.body != nil {
		switch v := options.body.(type) {
		case []byte:
			bodyReader = bytes.NewReader(v)
		case string:
			bodyReader = strings.NewReader(v)
		default:
			jsonData, err := json.Marshal(v)
			if err != nil {
				return nil, err
			}
			bodyReader = bytes.NewReader(jsonData)
		}
	}
	//创建请求
	req, err := http.NewRequestWithContext(options.ctx, method, u.String(), bodyReader)
	if err != nil {
		return nil, err
	}
	//设置请求头
	for k, v := range h.Headers {
		req.Header[k] = v
	}
	// 设置特定请求头
	for k, v := range options.headers {
		req.Header.Set(k, v)
	}
	// 如果是JSON body且没有指定Content-Type，默认设置为application/json
	if options.body != nil && req.Header.Get("Content-Type") == "" {
		req.Header.Set("Content-Type", "application/json")
	}
	// 记录请求日志
	log.Printf("Request: %s %s Headers: %v Body: %v", method, u.String(), req.Header, options.body)

	//执行请求
	resp, err := h.client.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	//读取响应体
	respBody, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	// 记录响应日志
	log.Printf("Response: Status: %d Body: %s", resp.StatusCode, string(respBody))
	//检查状态码
	if resp.StatusCode >= http.StatusBadRequest {
		return nil, &HTTPError{
			StatusCode: resp.StatusCode,
			Message:    string(respBody),
		}
	}
	return respBody, nil
}

// Get 发送 GET 请求
func (h *HTTPClient) Get(path string, opts ...RequestOption) ([]byte, error) {
	return h.Request(http.MethodGet, path, opts...)
}

// Post 发送 POST 请求
func (h *HTTPClient) Post(path string, opts ...RequestOption) ([]byte, error) {
	return h.Request(http.MethodPost, path, opts...)
}

// Put 发送 PUT 请求
func (h *HTTPClient) Put(path string, opts ...RequestOption) ([]byte, error) {
	return h.Request(http.MethodPut, path, opts...)
}

// Delete 发送 DELETE 请求
func (h *HTTPClient) Delete(path string, opts ...RequestOption) ([]byte, error) {
	return h.Request(http.MethodDelete, path, opts...)
}

// RequestWithRetry 执行请求并在失败时进行重试
// maxRetries 最大重试次数
func (h *HTTPClient) RequestWithRetry(method, path string, maxRetries int, opts ...RequestOption) ([]byte, error) {
	var lastErr error

	for i := 0; i < maxRetries; i++ {
		resp, err := h.Request(method, path, opts...)
		if err == nil {
			return resp, nil
		}
		// 如果是HTTP错误且状态码是5xx，则重试
		if httpErr, ok := err.(*HTTPError); ok && httpErr.StatusCode >= 500 {
			lastErr = err
			time.Sleep(time.Second * time.Duration(i+1)) // 指数退避
			continue
		}
		return nil, err // 其他错误直接返回
	}
	return nil, fmt.Errorf("after %d retries, last error: %w", maxRetries, lastErr)
}

// RequestJson 执行请求并将响应体解析到result中
func (c *HTTPClient) RequestJson(method, path string, result interface{}, opts ...RequestOption) error {
	respBody, err := c.Request(method, path, opts...)
	if err != nil {
		return err
	}

	if result != nil {
		if err := json.Unmarshal(respBody, result); err != nil {
			return fmt.Errorf("failed to unmarshal response: %w", err)
		}
	}

	return nil
}
